package peer;


import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.ftpserver.FtpServer;
import org.apache.ftpserver.FtpServerFactory;
import org.apache.ftpserver.ftplet.FtpException;
import org.apache.ftpserver.ftplet.Ftplet;
import org.apache.ftpserver.ftplet.UserManager;
import org.apache.ftpserver.listener.ListenerFactory;
import org.apache.ftpserver.usermanager.ClearTextPasswordEncryptor;
import org.apache.ftpserver.usermanager.PropertiesUserManagerFactory;
import org.apache.ftpserver.usermanager.impl.BaseUser;
import error.DFtpException;
import error.DgoogleException;
import file.LocalSharedFile;
import file.RemoteSharedFile;
import ftp.MyFtplet;



class PeerClient implements IPeerClient {

	public static final int DEF_FTP_PORT = 2221;
	String name;
	String passwd;
	String ip;
	short port;
	IPeerFactory factory;
	IBootProtocol boot_protocol;
	FtpServer server;
	MyFtplet ftp_worker;
        String human_user_name;
	
	public PeerClient(String name, String human_user_name, String passwd, String ip,
			short port, IPeerFactory factory, String email) {
		this.name = name;
		this.passwd = passwd;
		this.ip = ip;
		this.port = port;
		this.factory = factory;
                this.human_user_name = human_user_name;
		
		this.boot_protocol = factory.makeBootProtocol(name, human_user_name, passwd, ip, port, email);
	}

	public void login() throws DgoogleException {
		//No need to do try catch here.  It's upper level's duty.
		
		if(!boot_protocol.is_logined())
		{
			this.boot_protocol.login();
		}
		
		if(boot_protocol.is_logined())
		{
			try {
				this.start_ftp();
			} catch (FtpException e) {
				throw new DFtpException("Can not strart ftp server that serve file for other peers");
			}
		}
	}
   //call start_ftp in login(). if login success, start ftp. 
	private void start_ftp() throws FtpException {
		
		//a server can have multiple listener, a listener is to listen a certain port. 
		FtpServerFactory serverFactory = new FtpServerFactory();
        
		ListenerFactory factory = new ListenerFactory();
		        
		// 1) set the port of the listener
		factory.setPort(DEF_FTP_PORT);

		// replace the default listener
		serverFactory.addListener("default", factory.createListener());
		
		ftp_worker = new MyFtplet();
		
		Map<String, Ftplet> map_ftplest = new LinkedHashMap<String, Ftplet>();
		map_ftplest.put("default", ftp_worker);
		
		//2) set the call backfunction
		serverFactory.setFtplets(map_ftplest);
		
		/** @note
		 * File "conf/users.properties" MUST exist in application folder.
		 */
		PropertiesUserManagerFactory user_facttory = new PropertiesUserManagerFactory();
		//user_facttory.setFile(new File("users.properties"));
		
		ClearTextPasswordEncryptor passwordEncryptor = new ClearTextPasswordEncryptor();
		user_facttory.setPasswordEncryptor(passwordEncryptor);
		
		UserManager um = user_facttory.createUserManager();
		
		BaseUser user = new BaseUser();
		user.setName("anonymous");
		user.setPassword("");
		user.setHomeDirectory(".");
		
		um.save(user);
		
		//3) set user
		serverFactory.setUserManager(um);
		
		Boolean b = um.doesExist("anonymous");
		
		assert(b);

		// start the server
		server = serverFactory.createServer(); 
		        
		server.start();
	}

	public void close() {
		if(this.boot_protocol != null)
		{
			this.boot_protocol.close();		
			this.boot_protocol = null;		
		}		
		
		if(this.server != null)
		{
			this.server.stop();
		}
	}

	public void register() throws DgoogleException {
		if(!boot_protocol.is_registered())
		{
			this.boot_protocol.register();
		}
	}

	public void logoff() {
		this.close();
	}

	public void resetPassword()  throws DgoogleException{
		this.boot_protocol.resetPassword();
	}
	public void error(String type, String details) throws DgoogleException{
		this.boot_protocol.error(type, details);
	}
	
	public void changePassword(String new_pass)  throws DgoogleException{
		this.boot_protocol.changePassword(new_pass);
	}

	public void publish(String path) throws DgoogleException, IOException {
		LocalSharedFile f = this.boot_protocol.publish(path);
		
		//Add published file to FTP server
		//ftp_worker is class myftplet, declared in peerclient
		
		this.ftp_worker.addFile(f);
	}

	public List<RemoteSharedFile> query(String keyWord) throws DgoogleException {
		return this.boot_protocol.query(keyWord);
	}
	
	//use library of ftpserver-1.0.3/apache-ftpserver-1.0.3/common/lib/*.jar
	//and commons-net-2.0/*.jar
	public InputStream download(RemoteSharedFile file) throws DgoogleException, IOException
	{
		FTPClient ftp= new FTPClient();
		
		//return the ip address of the peer ip which stores the file
		ftp.connect(file.getIp(), DEF_FTP_PORT); 
		ftp.login("anonymous", "");
		
		/**
		 * @note FTP client must use passive mode which mean it's client 
		 * who opens TCP connection for file transfer.
		 */
		ftp.enterLocalPassiveMode();

		// After connection attempt, you should check the reply code to verify
		// success.
		int reply = ftp.getReplyCode();
		
		if(!FTPReply.isPositiveCompletion(reply)) {
			ftp.disconnect();
			throw new DFtpException("FTP server refused connection.");
		}
		
		return ftp.retrieveFileStream(file.name());

	}

	@Override
	public void unpublish(String name) {
		if(this.ftp_worker != null)
		{
			this.ftp_worker.removeFile(name);
		}
	}
}
